home *** CD-ROM | disk | FTP | other *** search
Wrap
/* --------------------------------------------------------------------------------- SoundLevel Ken Bereskin March 30, 1992 A simple application that echos the sound input level on the microphone using a display much like a VCR peak-hold bar meter. Its a fun app, to use up those wasted background cycles and seems to amuse people to no end. At 40k, its not a major memory hog so pop it in your Startup Items folder and enjoy. The About Box introduces a new power user short cut. Clap hard enough to raise the sound threshold about ≈ 95% and the dialog will dismiss. No need to reach and strain for that enter key anymore! Caveats: • Not much error checking. • Too many globals. • Saves the window position back to the app resource fork. • Requires a Sound Input Device (MacRecorder will do just fine) • It keeps the sound input device open with r/w so you won’t be able to use other sound input apps unless you quit. Have fun and let me know what you think. Copyright ©1992 Apple Canada Inc. */ /* --------------------------------------------------------------------------------- ToolBox header files */ #include <Types.h> #include <QuickDraw.h> #include <Resources.h> #include <Windows.h> #include <Fonts.h> #include <Events.h> #include <TextEdit.h> #include <Dialogs.h> #include <Menus.h> #include <Memory.h> #include <Desk.h> #include <ToolUtils.h> #include <OSUtils.h> #include <Errors.h> #include <Folders.h> #include <OSEvents.h> #include <SegLoad.h> #include <Traps.h> #include <SoundInput.h> #include <GestaltEqu.h> /* --------------------------------------------------------------------------------- Forward reference prototypes */ void Initialize(void); void SetupEnvironment(void); void SetupMemory(void); void SetupMenus(void); void DoEvents(void); void DoMouseDown (EventRecord *event); void DoKeyDown (EventRecord *event); void DoUpdateWindow (WindowPtr window); void DoActivateWindow (Boolean activate, WindowPtr window); void DoMenuCommand(short theMenu, short theItem); void DoContentClick(EventRecord *event, WindowPtr window); void DoSuspend(EventRecord *event); void DoResume(EventRecord *event); void DoIdle(void); void DoDeskAccessory(short whichMenu, short whichItem); void NewLevelWindow(void); void CleanUp(void); void DoAbout(void); Boolean OptionTest(void); Boolean CmdTest(void); Boolean ShiftTest(void); Boolean ColourQDExists (void); Boolean ColourQDIsOn (void); short ColourDepth (void); void TwitchToFinder(void); void DrawTheMeter(Rect *meterRect, short numElements, short *peakLevel, short *lastLevel, long *peakTimeout, short meterLevel, short redZone, RGBColor *blueRGB, RGBColor *redRGB, RGBColor *blackRGB, Boolean useColour); void GetBarRect(Rect *meterRect, short whichBar, Rect *barRect); void InitLevelMeter (void); void CloseLevelMeter (void); OSErr OpenTheSoundDevice(void); void DrawOneBar(Rect *meterRect, short whichBar, Boolean useColour); pascal char AboutFilterProc(DialogPtr dialog, EventRecord *event, short *item); void HiliteItem(DialogPtr dialog, short theitem); void UpdateAboutDialog(WindowPtr window); /* --------------------------------------------------------------------------------- Constants */ #define kMaxVolume 255 #define kPeakTimeoutTicks 60 // number of ticks needed to timeout peak #define kMenuBarID 128 #define kAppleMenu 128 #define kAboutItem 1 #define kFileMenu 129 #define kQuitItem 1 #define kGestaltLevelMeter 'SnLv' /* --------------------------------------------------------------------------------- Global variables */ Boolean WNEIsImplemented; // is WaitNextEvent avaiable Boolean gQuitFlag = false; // set to force a quit WindowPtr gLevelWindow; // sound level window pointer RGBColor blueRGB, redRGB, blackRGB; // rgb colours used for drawing meter short gNumElements; // number of elements in the meter long gPeakTimeOut; short gPeakLevel; short gLastLevel; short gRedZone; Rect gMeterRect; long gSoundInputRefNum; Boolean gUseColour; /* --------------------------------------------------------------------------------- Main Application main entry point */ main() { Initialize(); // do application initialization NewLevelWindow(); // create the level window InitLevelMeter(); // start up the level meter DoUpdateWindow(gLevelWindow); while (!gQuitFlag) // handle events until quit DoEvents(); CleanUp(); // clean up before quitting } /* --------------------------------------------------------------------------------- Intialize First time application initialization */ #pragma segment Initialize static void Initialize() { InitGraf(&qd.thePort); InitFonts(); InitWindows(); InitMenus(); TEInit(); InitDialogs(nil); InitCursor(); FlushEvents(everyEvent, 0); SetupMemory(); SetupEnvironment(); SetupMenus(); gUseColour = ColourDepth() >= 4; } /* --------------------------------------------------------------------------------- SetupEnvironment This routine will determine the hardware and software operating environment */ #define RequestedVersion 1 static void SetupEnvironment() { WNEIsImplemented = true; } /* --- SetupMemory ------------------------------------------------------------------------------------ This routine will allocate some master pointers, grow the heap, and set up a grow zone function. */ static void SetupMemory() { MaxApplZone(); MoreMasters(); MoreMasters(); } /* --- SetupMenus ------------------------------------------------------------------------------------ This routine will set up the LeadSheet menus. */ static void SetupMenus() { Handle menuBarHndl; // load up the apple and file menu menuBarHndl = GetNewMBar(kMenuBarID); // read menus into menu bar SetMenuBar(menuBarHndl); // install menus DisposHandle(menuBarHndl); AddResMenu(GetMHandle(kAppleMenu), 'DRVR'); // add DA names to Apple menu DrawMenuBar(); } #pragma segment Main /* --------------------------------------------------------------------------------- DoEvents This is the LaunchPad Main Event Loop. We fetch events from the event queue, and call the proper handlers for the given event class. */ #define kSleepTicks 0 static void DoEvents() { Boolean newEvent; // is there an event in the queue EventRecord event; // the event record /* Look for an event in the event queue. */ if (WNEIsImplemented) { newEvent = WaitNextEvent(everyEvent, &event, kSleepTicks, nil); } else { SystemTask(); newEvent = GetNextEvent(everyEvent, &event); } DoIdle(); // idle processing /* Dispatch on the type of event */ switch(event.what) { case mouseDown: DoMouseDown(&event); break; case keyDown: case autoKey: DoKeyDown(&event); break; case updateEvt: DoUpdateWindow((WindowPtr)event.message); break; case activateEvt: DoActivateWindow(event.modifiers & activeFlag, (WindowPtr)event.message); break; case diskEvt: break; case osEvt: /* Check for suspend or resume events (the only ones now defined) */ if ((event.message >> 24) == suspendResumeMessage) { if (event.message & resumeFlag) DoResume(&event); else DoSuspend(&event); } break; case highLevelEventMask: break; } DoIdle(); // more idle processing } /* --- DoMouseDown ---------------------------------------------------------------------------------- This routine handles mouseDown events. */ static void DoMouseDown(event) EventRecord *event; { short whichPart; WindowPtr window; Rect dragBounds; long selection; short theMenu, theItem; /* Determine where the mouseDown event occurred */ whichPart = FindWindow(event->where, &window); switch(whichPart) { case inMenuBar: selection = MenuSelect(event->where); theMenu = HiWord(selection); theItem = LoWord(selection); DoMenuCommand(theMenu, theItem); break; case inSysWindow: SystemClick(event, window); break; case inContent: DoContentClick(event, window); break; case inDrag: dragBounds = qd.screenBits.bounds; DragWindow(window, event->where, &dragBounds); break; case inGrow: break; case inGoAway: break; case inZoomIn: case inZoomOut: break; case inDesk: // a click in the desktop will deselect the current selection break; } } /* --------------------------------------------------------------------------------- DoKeyDown This routine handles keyDown events. */ static void DoKeyDown (EventRecord *event) { #pragma unused(event) gQuitFlag = true; } /* --------------------------------------------------------------------------------- DoUpdateWindow This routine handles update events for the given window */ static void DoUpdateWindow (window) WindowPtr window; { GrafPtr savePort; Rect boundsRect; short i; GetPort(&savePort); BeginUpdate(window); // set up the clipRgn and visRgn SetPort(window); RGBForeColor(&blackRGB); EraseRect(&window->portRect); boundsRect = window->portRect; InsetRect(&boundsRect, 2, 2); PaintRect(&boundsRect); // redraw the bar graph frames up to the current level for (i = 1; i <= gLastLevel; i++) { DrawOneBar(&gMeterRect, i, gUseColour); } EndUpdate(window); // restore window regions SetPort(savePort); } /* --- DoActivateWindow ---------------------------------------------------------------------------------- This routine handles activate events for the given window */ static void DoActivateWindow (activate, window) Boolean activate; WindowPtr window; { #pragma unused(activate, window) } /* --------------------------------------------------------------------------------- DoMenuCommand This routine handles menu selection dispatching */ static void DoMenuCommand(short theMenu, short theItem) { switch (theMenu) { case kAppleMenu: if (theItem == kAboutItem) { DoAbout(); } else { DoDeskAccessory(theMenu, theItem); } break; case kFileMenu: if (theItem == kQuitItem) { ExitToShell(); } break; default: break; } HiliteMenu(0); } /* --- DoContentClick ------------------------------------------------------------------------------ This routine handles clicks in the content area of a window. If the option key is down, drag the launch window to another location on the screen. */ static void DoContentClick(event, window) EventRecord *event; WindowPtr window; { #pragma unused(event) GrafPtr savePort; Point clickPt; /* If the clicked window isn't frontmost, simply select it */ if (window != FrontWindow()) { SelectWindow(window); return; } GetPort(&savePort); // save the current port SetPort(window); // set the port to the clicked window clickPt = event->where; // convert point to local coordindates GlobalToLocal(&clickPt); DragWindow(window, event->where, &qd.screenBits.bounds); SetPort(savePort); // restore the saved port } /* --------------------------------------------------------------------------------- DoSuspend This routine handles multifinder suspend events. */ static void DoSuspend(event) EventRecord *event; { #pragma unused(event) DoActivateWindow(false, gLevelWindow); } /* --------------------------------------------------------------------------------- DoResume This routine handles multifinder resume events. */ static void DoResume(event) EventRecord *event; { #pragma unused(event) InitCursor(); DoActivateWindow(true, gLevelWindow); } /* --------------------------------------------------------------------------------- DoDeskAccessory Handle desk accessory selections from the apple menu */ static void DoDeskAccessory(whichMenu, whichItem) short whichMenu; short whichItem; { Str255 daName; GetItem(GetMHandle(whichMenu), whichItem, daName); (void) OpenDeskAcc(daName); } /* --------------------------------------------------------------------------------- NewLevelWindow This routine will create the sound level window. */ static void NewLevelWindow() { Point **userOffsetPtHndl; Rect screenRect; Rect globalRect; if (ColourQDExists()) gLevelWindow = GetNewCWindow(128, nil, (WindowPtr)-1); else gLevelWindow = GetNewWindow(128, nil, (WindowPtr)-1); SetPort(gLevelWindow); // load the offset resource that tells us if the user has dragged // the window somewhere else. make sure the window remains visible // and eventually put this in a pref file where it belongs userOffsetPtHndl = (Point **)GetResource('WPos', 128); if (userOffsetPtHndl != nil) { screenRect = qd.screenBits.bounds; InsetRect(&screenRect, 5, 5); // a little bit of a margin // the port is set for our window, so convert (0,0) to global for the // global topLeft of the window globalRect.left = (**userOffsetPtHndl).h; globalRect.top = (**userOffsetPtHndl).v; globalRect.right = globalRect.left + (gLevelWindow->portRect.right - gLevelWindow->portRect.left); globalRect.bottom = globalRect.top + (gLevelWindow->portRect.bottom - gLevelWindow->portRect.top); if (SectRect(&screenRect, &globalRect, &globalRect)) { MoveWindow(gLevelWindow, globalRect.left, globalRect.top, true); } ReleaseResource((Handle)userOffsetPtHndl); } ShowWindow(gLevelWindow); // make it visible } /* --------------------------------------------------------------------------------- CleanUp This routine will clean up before exiting to the Finder */ static void CleanUp() { Point **userOffsetPtHndl; Point globalPt; CloseLevelMeter(); // close up the level meter // save the position of the top/left corner of the window in case the user // has moved it. For now, back to the WPos resource in the app will have to do userOffsetPtHndl = (Point **)GetResource('WPos', 128); if (userOffsetPtHndl != nil) { SetPort(gLevelWindow); globalPt.h = globalPt.v = 0; LocalToGlobal(&globalPt); **userOffsetPtHndl = globalPt; ChangedResource((Handle)userOffsetPtHndl); WriteResource((Handle)userOffsetPtHndl); } if (gLevelWindow != nil) DisposeWindow(gLevelWindow); } /* --------------------------------------------------------------------------------- MakeCursor This routine will set the cursor to the 'CURS' resource whose id is given. */ void MakeCursor(cursorID) short cursorID; { #pragma unused(cursorID) } /* --------------------------------------------------------------------------------- DoIdle */ static void DoIdle() { OSErr err; short recordingStatus = 0; // status of recording session short meterLevel = 0; // current meter level unsigned long totalSamplesToRecord = 0; // total number of samples unsigned long numberOfSamplesRecorded = 0; // number of samples recorded unsigned long totalMsecsToRecord; unsigned long numberOfMsecsRecorded; GrafPtr savePort; GetPort(&savePort); SetPort(gLevelWindow); /* get the sound input status */ err = SPBGetRecordingStatus(gSoundInputRefNum, &recordingStatus, &meterLevel, &totalSamplesToRecord, &numberOfSamplesRecorded, &totalMsecsToRecord, &numberOfMsecsRecorded); DrawTheMeter(&gMeterRect, gNumElements, &gPeakLevel, &gLastLevel, &gPeakTimeOut, meterLevel, gRedZone, &blueRGB, &redRGB, &blackRGB, gUseColour); SetPort(savePort); } /* --------------------------------------------------------------------------------- OptionTest Returns whether the option key is being pressed */ static Boolean OptionTest() { KeyMap theKeys; GetKeys(&theKeys); if (theKeys[1] & 4) return(true); else return(false); } /* --------------------------------------------------------------------------------- CmdTest Returns whether the command (apple) key is being pressed */ static Boolean CmdTest() { KeyMap theKeys; GetKeys(&theKeys); if (theKeys[1] & 0x8000) return(true); else return(false); } /* --------------------------------------------------------------------------------- ShiftTest Returns whether the shift key is being pressed */ static Boolean ShiftTest() { KeyMap theKeys; GetKeys(&theKeys); if (theKeys[1] & 1) return(true); else return(false); } #define ROM85 (*(short *)0x28e) #define TWOHIGHMASK 0xc000 #define COLOURQDEXISTS !(ROM85 & TWOHIGHMASK) /* --------------------------------------------------------------------------------- ColourQDExists This routine is called to determine whether or not color quickdraw exists in the current roms. It checks the lom memory global just like Apple usually does… */ Boolean ColourQDExists() { return(COLOURQDEXISTS); } /* --------------------------------------------------------------------------------- ColourQDIsOn This routine is called to determine if colour quickdraw exists in the current roms and if the main screen is displaying color. */ #define QD_MIN_FOR_COLOUR 4 Boolean ColourQDIsOn() { GDHandle maindevice; PixMapHandle mainpix; short depth; /* First confirm that color is available in the roms */ if (COLOURQDEXISTS) { /* Determine if the main device is displaying color */ maindevice = GetMainDevice(); mainpix = (*maindevice)->gdPMap; /* Check the depth of the pixel map */ depth = (*mainpix)->pixelSize; if (depth < QD_MIN_FOR_COLOUR) return(false); else return(true); } else return(false); } short ColourDepth() { GDHandle maindevice; PixMapHandle mainpix; /* First confirm that color is available in the roms */ if (COLOURQDEXISTS) { /* Determine if the main device is displaying color */ maindevice = GetMainDevice(); mainpix = (*maindevice)->gdPMap; /* Check the depth of the pixel map */ return (*mainpix)->pixelSize; } else return 1; } /* --------------------------------------------------------------------------------- TwitchToFinder Switch back to Finder. Use the System 7.0 process manager if present */ static void TwitchToFinder() { } /* --------------------------------------------------------------------------------- DrawTheMeter This routine is called to draw the volume meter during sound input. */ void DrawTheMeter(Rect *meterRect, short numElements, short *peakLevel, short *lastLevel, long *peakTimeout, short meterLevel, short redZone, RGBColor *blueRGB, RGBColor *redRGB, RGBColor *blackRGB, Boolean useColour) { Rect barRect; short i; PenNormal(); // quantize the meterLevel based on number of elements // in our bar chart if (meterLevel > kMaxVolume) meterLevel = kMaxVolume; meterLevel = (meterLevel * numElements) / kMaxVolume; // determine if this is a new peak value and erase the // peak bar if it has timed out if (meterLevel >= *peakLevel) { *peakLevel = meterLevel; *peakTimeout = TickCount() + kPeakTimeoutTicks; } else { // has the current peak timed out? if (*peakLevel > 0 && *peakTimeout <= TickCount()) { if (*peakLevel >= *lastLevel) { if (useColour) RGBForeColor(blackRGB); GetBarRect(meterRect, *peakLevel, &barRect); PaintRect(&barRect); // erase the previous element (peak is two elements wide) if (*peakLevel > 1) { // don’t erase a non-element GetBarRect(meterRect, *peakLevel - 1, &barRect); PaintRect(&barRect); } } *peakLevel = 0; } } // check whether the signal is now stronger than last time if (meterLevel > *lastLevel) { for (i = *lastLevel + 1; i <= meterLevel; i++) { GetBarRect(meterRect, i, &barRect); // get the rect for this bar if (useColour) { // watch out for colour if (i >= redZone) // are we beyond the clipping point? RGBForeColor(redRGB); // draw in red to show distortion else RGBForeColor(blueRGB); // draw in blue for normal signal PaintRect(&barRect); // fill the element in } else EraseRect(&barRect); // no colour so white will have to do } *lastLevel = meterLevel; // remember for next time } // check whether the signal level is now weaker than last time // and if it is remove the rightmost two level bar currently on if (meterLevel < *lastLevel) { if (*peakLevel != 0) { if (*lastLevel == *peakLevel) // don’t erase the peak! *lastLevel -= 2; if (*lastLevel == *peakLevel -1) *lastLevel -= 1; } if (useColour) RGBForeColor(blackRGB); for (i = 0; i < 2; i++) { if (*lastLevel > 0) { GetBarRect(meterRect, *lastLevel, &barRect); PaintRect(&barRect); *lastLevel -= 1; } if (*lastLevel < 0 ) *lastLevel = 0; } } if (useColour) RGBForeColor(blackRGB); } /* --------------------------------------------------------------------------------- GetBarRect */ void GetBarRect(Rect *meterRect, short whichBar, Rect *barRect) { if (whichBar == 0) { return; } SetRect(barRect, meterRect->left + 2, meterRect->top + 1, meterRect->left + 4, meterRect->bottom - 1); OffsetRect(barRect, (whichBar - 1)* 3, 0); } /* ------------------------------------------------------------------------------- InitLevelMeter */ void InitLevelMeter() { RGBColor **colorRsrcHndl; OSErr err; // load the colours used for the bar chart colorRsrcHndl = (RGBColor **)GetResource(128, 'RGBv'); if (colorRsrcHndl) { blueRGB = **colorRsrcHndl; ReleaseResource((Handle)colorRsrcHndl); } else { blueRGB.red = 39321; // flourescant blue blueRGB.green = 65535; blueRGB.blue = 65535; } colorRsrcHndl = (RGBColor **)GetResource(129, 'RGBv'); if (colorRsrcHndl) { redRGB = **colorRsrcHndl; ReleaseResource((Handle)colorRsrcHndl); } else { redRGB.red = 65535; // peak level red redRGB.green = 0; redRGB.blue = 0; } blackRGB.red = 0; blackRGB.green = 0; blackRGB.blue = 0; gMeterRect = gLevelWindow->portRect; InsetRect(&gMeterRect, 2, 2); gNumElements = (gMeterRect.right - gMeterRect.left - 2) / 3; if (gNumElements > 255) gNumElements = 255; gPeakTimeOut = 0; gPeakLevel = 0; gLastLevel = 0; gRedZone = (gNumElements * 2) / 3; // open the sound input device err = OpenTheSoundDevice(); } // watch out for other usages of the soundInputRefNum static void CloseLevelMeter() { OSErr err; err = SPBCloseDevice(gSoundInputRefNum); gSoundInputRefNum = 0; } #define kAboutDLOG 128 #define kAboutMeterItem 4 short gAboutPeakLevel; short gAboutLastLevel; long gAboutPeakTimeOut; void DoAbout() { DialogPtr dialog; short itemHit; GrafPtr savePort; GetPort(&savePort); dialog = GetNewDialog(kAboutDLOG, nil, (WindowPtr)-1); SetPort(dialog); // ••• force an update event first UpdateAboutDialog((WindowPtr)dialog); gAboutPeakLevel = 0; gAboutLastLevel = 0; gAboutPeakTimeOut = 0; do { ModalDialog((ModalFilterProcPtr)AboutFilterProc, &itemHit); } while (itemHit != ok); SetPort(savePort); DisposDialog(dialog); } OSErr OpenTheSoundDevice() { OSErr err; short meterState; // set the default zone to the system heap so that the SI manager will // possibly get fooled into allocating there instead of my heap SetZone(SystemZone()); err = SPBOpenDevice(nil, siWritePermission, &gSoundInputRefNum); if (err != noErr) goto BailOut; // turn on sound metering meterState = 1; // turn it on err = SPBSetDeviceInfo(gSoundInputRefNum, siLevelMeterOnOff, (char *)&meterState); BailOut: SetZone(ApplicZone()); return err; } void DrawOneBar(Rect *meterRect, short whichBar, Boolean useColour) { Rect barRect; GetBarRect(meterRect, whichBar, &barRect); // get the rect for this bar if (useColour) { // watch out for colour if (whichBar >= gRedZone) // are we beyond the clipping point? RGBForeColor(&redRGB); // draw in red to show distortion else RGBForeColor(&blueRGB); // draw in blue for normal signal PaintRect(&barRect); // fill the element in } else EraseRect(&barRect); // no colour so white will have to do } /* --------------------------------------------------------------------------------- AboutFilterProc */ #define CRKey 0x0d #define EnterKey 0x03 #define EscapeKey 0x1b pascal char AboutFilterProc(DialogPtr dialog, EventRecord *event, short *item) { unsigned char theKey; WindowPtr window; short itemType; Handle itemHndl; Rect itemRect; OSErr err; short recordingStatus = 0; // status of recording session short meterLevel = 0; // current meter level unsigned long totalSamplesToRecord = 0; // total number of samples unsigned long numberOfSamplesRecorded = 0; // number of samples recorded unsigned long totalMsecsToRecord; unsigned long numberOfMsecsRecorded; /* get the sound input status */ if (event->what != updateEvt) { err = SPBGetRecordingStatus(gSoundInputRefNum, &recordingStatus, &meterLevel, &totalSamplesToRecord, &numberOfSamplesRecorded, &totalMsecsToRecord, &numberOfMsecsRecorded); GetDItem(dialog, kAboutMeterItem, &itemType, &itemHndl, &itemRect); InsetRect(&itemRect, 2, 2); SetPort(dialog); DrawTheMeter(&itemRect, gNumElements, &gAboutPeakLevel, &gAboutLastLevel, &gAboutPeakTimeOut, meterLevel, gRedZone, &blueRGB, &redRGB, &blackRGB, gUseColour); // check for nearly maximum volume to dismiss dialog; if (meterLevel > 250) { HiliteItem(dialog, ok); *item = ok; return true; } } switch(event->what) { case mouseDown: break; case keyDown: case autoKey: theKey = event->message & charCodeMask; if (theKey == CRKey || theKey == EnterKey || theKey == EscapeKey) { HiliteItem(dialog, ok); *item = ok; return true; } break; case updateEvt: window = (WindowPtr)event->message; if (window == (WindowPtr)dialog) { UpdateAboutDialog(window); } break; default: break; } return false; } void UpdateAboutDialog(WindowPtr window) { GrafPtr savePort; short itemType; Handle itemHndl; Rect itemRect; BeginUpdate(window); GetPort(&savePort); SetPort(window); UpdtDialog((DialogPtr)window, window->visRgn); PenNormal(); GetDItem((DialogPtr)window, kAboutMeterItem, &itemType, &itemHndl, &itemRect); FrameRect(&itemRect); InsetRect(&itemRect, 2, 2); PaintRect(&itemRect); SetPort(savePort); EndUpdate(window); } /* --------------------------------------------------------------------------------- HiliteItem This routine will hilite the given item control so the user knows which item has been activated by the key equivalent */ #define ButtonInvertState 10 // inverted state for push buttons #define CheckRadioState 11 // inverted state for checks and radios #define NormalState 0 // non-inverted state #define SleepDuration 10 // pause for 10 ticks void HiliteItem(DialogPtr dialog, short theitem) { Handle thehndl; short thetype; Rect thebox; long junkTicks; short hiliteState; /* Get Handle to the item */ GetDItem(dialog, theitem, &thetype, &thehndl, &thebox); switch (thetype) { case btnCtrl + ctrlItem: hiliteState = ButtonInvertState; break; case chkCtrl + ctrlItem: case radCtrl + ctrlItem: hiliteState = CheckRadioState; break; default: return; break; } /* Invert the control for a little while */ HiliteControl((ControlHandle)thehndl, hiliteState); Delay(SleepDuration, &junkTicks); HiliteControl((ControlHandle)thehndl, NormalState); }